package com.ias.redis.support;

import com.ias.redis.config.CacheRedisProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.support.AbstractValueAdaptingCache;
import org.springframework.cache.support.SimpleValueWrapper;
import org.springframework.data.redis.core.RedisTemplate;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;

@Slf4j
public class RedisCache extends AbstractValueAdaptingCache {

    private String name;

    private RedisTemplate<Object, Object> redisTemplate;

    private String cachePrefix;

    /**
     * 默认key超时时间 1h:3600s
     */
    private long defaultExpiration = 3600;

    private Map<String, Long> defaultExpires = new HashMap<>();

    {
        defaultExpires.put(CacheNames.CACHE_1MIN, TimeUnit.MINUTES.toSeconds(1));
        defaultExpires.put(CacheNames.CACHE_5MINS, TimeUnit.MINUTES.toSeconds(5));
        defaultExpires.put(CacheNames.CACHE_15MINS, TimeUnit.MINUTES.toSeconds(15));
        defaultExpires.put(CacheNames.CACHE_60MINS, TimeUnit.MINUTES.toSeconds(60));
        defaultExpires.put(CacheNames.CACHE_12HOUR, TimeUnit.HOURS.toSeconds(12));
        defaultExpires.put(CacheNames.CACHE_24HOUR, TimeUnit.HOURS.toSeconds(24));
    }

    private Boolean logShowValue = true;

    protected RedisCache(boolean allowNullValues) {
        super(allowNullValues);
    }

    public RedisCache(String name, RedisTemplate<Object, Object> redisTemplate,
                      CacheRedisProperties cacheRedisProperties) {
        super(cacheRedisProperties.isCacheNullValues());
        this.name = name;
        this.redisTemplate = redisTemplate;
        this.cachePrefix = cacheRedisProperties.getCachePrefix();
        this.defaultExpiration = cacheRedisProperties.getRedis().getDefaultExpiration();
        this.logShowValue = cacheRedisProperties.isLogShowValue();
        defaultExpires.putAll(cacheRedisProperties.getRedis().getExpires());
    }

    @Override
    public String getName() {
        return this.name;
    }

    @Override
    public Object getNativeCache() {
        return this.redisTemplate;
    }

    @Override
    public <T> T get(Object key, Callable<T> valueLoader) {
        ValueWrapper result = get(key);
        if (result != null) {
            return (T) result.get();
        }

        synchronized (key.toString().intern()) {
            result = get(key);
            if (result != null) {
                return (T) result.get();
            }

            try {
                T value = valueLoader.call();
                put(key, value);
                return value;
            } catch (Exception e) {
                throw new RuntimeException("Failed to load value for key: " + key, e);
            }
        }
    }

    @Override
    public void put(Object key, Object value) {
        if (!super.isAllowNullValues() && value == null) {
            this.evict(key);
            return;
        }

        long expire = getExpire();
        if (logShowValue) {
            log.info("[L1_CacheManager] RedisCache put cache, the key is : {}, the value is : {}, expire : {}", this.getKey(key), value, expire);
        } else {
            log.info("[L1_CacheManager] RedisCache put cache, the key is : {}, expire : {}", this.getKey(key), expire);
        }

        if (expire > 0) {
            redisTemplate.opsForValue().set(getKey(key), toStoreValue(value), expire, TimeUnit.SECONDS);
        } else {
            redisTemplate.opsForValue().set(getKey(key), toStoreValue(value));
        }
    }

    @Override
    public ValueWrapper putIfAbsent(Object key, Object value) {
        Object cacheKey = getKey(key);
        Object existing = redisTemplate.opsForValue().get(cacheKey);
        if (existing != null) {
            return new SimpleValueWrapper(fromStoreValue(existing));
        }

        put(key, value);
        return null;
    }

    @Override
    public void evict(Object key) {
        log.info("[L1_CacheManager] RedisCache evict cache, the key is : {}", this.getKey(key));
        redisTemplate.delete(this.getKey(key));
    }

    @Override
    public void clear() {
        log.info("[L1_CacheManager] RedisCache clear all cache");
        // 使用scan命令删除所有匹配的key
        String pattern = this.cachePrefix + ":" + this.name + ":*";
        redisTemplate.delete(redisTemplate.keys(pattern));
    }

    @Override
    protected Object lookup(Object key) {
        Object cacheKey = this.getKey(key);
        Object result = redisTemplate.opsForValue().get(cacheKey);
        if (logShowValue) {
            log.info("[L1_CacheManager] RedisCache lookup cache, the key is : {}, the value is : {}", cacheKey, result);
        } else {
            log.info("[L1_CacheManager] RedisCache lookup cache, the key is : {}", cacheKey);
        }
        return result;
    }

    private Object getKey(Object key) {
        return this.cachePrefix + ":" + this.name + ":" + key.toString();
    }

    private long getExpire() {
        long expire = defaultExpiration;
        Long cacheNameExpire = defaultExpires.get(this.name);
        return cacheNameExpire == null ? expire : cacheNameExpire;
    }
} 